﻿using System;
using System.ComponentModel;
using System.Windows.Threading;
using MvvmFx.Common.Helpers;

namespace MvvmFx.Common.ViewModels.Commands
{
    /// <summary>
    /// Base ViewModelCommandAsync is a generic class that inherits from 
    /// <see cref="ViewModelCommand"/> and allows ViewModel-level 
    /// functionality to execute asynchronusly when the commmand is executed.
    /// </summary>
    /// <typeparam name="TViewModel">The type of the  view model associated to the command.</typeparam>
    public abstract partial class ViewModelCommandAsync<TViewModel> :
        ViewModelCommand<TViewModel> where TViewModel : ViewModelBase
    {
        #region [ Events ]

        /// <summary>
        /// Event <see cref="EventHandler{ItemEventArgs<TViewModel>}"/> informing of current asyn task has started.
        /// </summary>
        public event EventHandler<ItemEventArgs<TViewModel>> Started;

        /// <summary>
        /// Event <see cref="EventHandler{ItemEventArgs<TViewModel>}"/> informing of current asyn task has completed.
        /// </summary>
        public event EventHandler<ItemEventArgs<TViewModel>> Completed;

        /// <summary>
        /// Event <see cref="EventHandler{ItemEventArgs<TViewModel>}"/> informing of current asyn task has failed.
        /// </summary>
        public event EventHandler<ItemEventArgs<TViewModel>> Failed;

        /// <summary>
        /// Event <see cref="EventHandler{ItemEventArgs<TViewModel>}"/> informing of current asyn task has cancelled.
        /// </summary>
        public event EventHandler<ItemEventArgs<TViewModel>> Cancelled;

        /// <summary>
        /// Event <see cref="EventHandler{ItemEventArgs<TViewModel>}"/> informing of current asyn progress.
        /// </summary>
        public event EventHandler<ProgressEventArgs> ProgressChanged;

        #endregion

        #region [ Private Members ]

        /// <summary>
        /// Background worker to perform the asynch task.
        /// </summary>
        private readonly BackgroundWorker _worker;

        /// <summary>
        /// Current dispatcher to ensure that we are executing on the right task.
        /// </summary>
        private readonly Dispatcher _dispatcher;

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelCommandAsync{TViewModel}"/> class.
        /// </summary>
        /// <param name="viewModel">The viewmodel associated to this document action view model command.</param>
        /// <param name="name">The name of the action.</param>
        protected ViewModelCommandAsync(TViewModel viewModel, string name)
            : base(viewModel)
        {

            Name = name;
            _dispatcher = DispatcherHelper.GetCurrent();

            _worker = new BackgroundWorker
                          {
                              WorkerSupportsCancellation = true,
                              WorkerReportsProgress = true
                          };

            _worker.DoWork += DoWork;
            _worker.RunWorkerCompleted += OnRunWorkerCompleted;
            _worker.ProgressChanged += OnWorkerProgressChanged;
        }

        #region [ Protected Methods ]

        /// <summary>
        /// Override the on execute to perform the action asynchronusly.
        /// </summary>
        /// <param name="parameter">The paramtere passed to the command.</param>
        protected sealed override void OnExecute(object parameter)
        {
            if (_worker.IsBusy)
            {
                //Cancel the task at hand.
                //
                _worker.CancelAsync();
                return;
            }

            //Start the _worker to carry out the task
            //
            _worker.RunWorkerAsync(parameter);
        }

        /// <summary>
        /// Override the can execute to determine if the operation can be executed.
        /// </summary>
        /// <returns>If the command can be executed.</returns>
        /// <param name="parameter">The parameter passed to the command.</param>
        protected sealed override bool OnCanExecute(object parameter)
        {
            return !_worker.IsBusy;
        }


        /// <summary>
        /// Override in derived class to provide the operation to execute.
        /// </summary>
        /// <param name="parameter">The parameter passed to the command.</param>
        protected virtual void OnExecuteAsync(object parameter)
        {
            //NOTE: Override in derived class.
        }

        #endregion

        #region [ Public Methods ]

        /// <summary>
        /// Cancel async the operation.
        /// </summary>
        public void CancelAsync()
        {
            if (_worker != null && _worker.IsBusy)
            {
                _worker.CancelAsync();
            }
        }

        /// <summary>
        /// Update the specified background _worker's progress.
        /// </summary>
        /// <param name="current">The current task out of a total number of tasks.</param>
        /// <param name="total">The total tasks to perform.</param>
        public void UpdateProgress(int current, int total)
        {
            if (_worker == null)
            {
                return;
            }

            //Calculate the progress as an int.
            //
            int percentToInt = CalculateCompletedPercentage(current, total);

            if (percentToInt > 0)
            {
                //Update the progress.
                //
                _worker.ReportProgress(percentToInt);
            }
        }

        #endregion

        #region [ Public Properties ]

        /// <summary>
        /// Gets the value of the <see cref="IsBusy"/> property.
        /// </summary>
        public bool IsBusy
        {
            get
            {
                if (_worker != null)
                {
                    return _worker.IsBusy;
                }
                return false;
            }
        }

        /// <summary>
        /// Gets the value of the <see cref="IsCancelPending"/> property.
        /// </summary>
        public bool IsCancelPending
        {
            get
            {
                if (_worker != null)
                {
                    //Check if we have a cancel pending.
                    //
                    if (_worker.CancellationPending)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        /// <summary>
        /// Gets or sets the value of the <see cref="Name"/> property.
        /// </summary>
        public string Name { get; private set; }

        #endregion

        #region [ Private Methods ]

        /// <summary>
        /// Calcualtes the current progress.
        /// </summary>
        /// <param name="current">The current progress.</param>
        /// <param name="total">The total progress.</param>
        /// <returns>The completed percentate </returns>
        public int CalculateCompletedPercentage(int current, int total)
        {
            //Report the current progress.
            //
            double percent = ((double)current / total);
            percent *= 100;
            return Convert.ToInt32(percent);
        }

        /// <summary>
        /// On the started async operation.
        /// </summary>
        private void OnStarted()
        {
            //Ensure we are on the correct thread.
            //
            if (!_dispatcher.CheckAccess())
            {
                _dispatcher.BeginInvoke(new Action(OnStarted));
                return;
            }

            //Notify of the async operation started.
            //
            EventHandler<ItemEventArgs<TViewModel>> tempHandler = Started;
            if (tempHandler != null)
            {
                tempHandler(this, new ItemEventArgs<TViewModel>(ViewModel));
            }
        }

        /// <summary>
        /// On the async operation cancelled, inform the listening object(s).
        /// </summary>
        private void OnCancelled()
        {
            //Ensure we are on the correct thread.
            //
            if (!_dispatcher.CheckAccess())
            {
                _dispatcher.BeginInvoke(new Action(OnCancelled));
                return;
            }

            //Notify of the async operation cancelled.
            //
            EventHandler<ItemEventArgs<TViewModel>> tempHandler = Cancelled;
            if (tempHandler != null)
            {
                tempHandler(this, new ItemEventArgs<TViewModel>(ViewModel));
            }
        }

        /// <summary>
        /// On the async operation failed, inform the listening object(s).
        /// </summary>
        private void OnFailed()
        {
            //Ensure we are on the correct thread.
            if (!_dispatcher.CheckAccess())
            {
                _dispatcher.BeginInvoke(new Action(OnFailed));
                return;
            }

            //Notify of the async operation failed.
            //
            EventHandler<ItemEventArgs<TViewModel>> tempHandler = Failed;
            if (tempHandler != null)
            {
                tempHandler(this, new ItemEventArgs<TViewModel>(ViewModel));
            }
        }

        /// <summary>
        /// On the async operation completed, inform the listening object(s).
        /// </summary>
        private void OnCompleted()
        {
            //Ensure we are on the correct thread.
            //
            if (!_dispatcher.CheckAccess())
            {
                _dispatcher.BeginInvoke(new Action(OnCompleted));
                return;
            }

            //Notify of the async operation completed.
            //
            EventHandler<ItemEventArgs<TViewModel>> completed = Completed;
            if (completed != null)
            {
                completed(this, new ItemEventArgs<TViewModel>(ViewModel));
            }
        }

        /// <summary>
        /// On the async operation progress changed, inform the listening object(s).
        /// </summary>
        /// <param name="currentProgress">The current async operation progress.</param>
        private void OnProgressChanged(int currentProgress)
        {
            //Ensure we are on the correct thread.
            //
            if (!_dispatcher.CheckAccess())
            {
                _dispatcher.BeginInvoke(new Action(() => OnProgressChanged(currentProgress)));
                return;
            }

            EventHandler<ProgressEventArgs> changed = ProgressChanged;
            if (changed != null) changed(this, new ProgressEventArgs(currentProgress));
        }

        /// <summary>
        ///  Dispose of the background worker performing.
        /// </summary>
        private void TidyBackgroundWorker()
        {
            if (_worker == null)
            {
                return;
            }

            //Ensure we cancel the task if it is still busy.
            //
            if (_worker.IsBusy)
            {
                _worker.CancelAsync();
            }

            _worker.DoWork -= DoWork;
            _worker.RunWorkerCompleted -= OnRunWorkerCompleted;
            _worker.ProgressChanged -= OnWorkerProgressChanged;

            DisposeBackgroundWorker();
        }

        #endregion

        #region [ IDisposable Members ]

        /// <summary>
        /// If to dispose state field.
        /// </summary>
        private bool _disposed;

        /// <summary>
        /// Clean up resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            // Use SupressFinalize in case a subclass
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Clean up resources.
        /// </summary>
        /// <param name="disposing">If disposing.</param>
        protected virtual void Dispose(bool disposing)
        {
            // If you need thread safety, use a lock around these 
            // operations, as well as in your methods that use the resource.
            if (!_disposed)
            {
                if (disposing)
                {
                    TidyBackgroundWorker();
                }

                // Indicate that the instance has been disposed.
                _disposed = true;
            }
        }

        #endregion

        #region [ Event Handlers ]

        /// <summary>
        /// Occurs when the <see cref="BackgroundWorker.ProgressChanged"/> changes. Perform the operation.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> object that fired the event.</param>
        /// <param name="e">The <see cref="DoWorkEventArgs"/> event fired.</param>
        void DoWork(object sender, DoWorkEventArgs e)
        {
            object parameter = e.Argument;

            _dispatcher.BeginInvoke(new Action(OnCanExecuteChanged));

            //Start the execution process.
            OnStarted();

            //Execute the async operation.
            OnExecuteAsync(parameter);

            //Check if we have a cancel pending.
            if (_worker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }

            //No cancels pending.
            e.Cancel = false;
        }

        /// <summary>
        /// Occurs when the <see cref="BackgroundWorker.ProgressChanged"/> changes. Report the current progress.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> object that fired the event.</param>
        /// <param name="e">The <see cref="ProgressChangedEventArgs"/> event fired.</param>
        void OnWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //Report the current async progress.
            OnProgressChanged(e.ProgressPercentage);
        }



        /// <summary>
        /// Occurs when the <see cref="BackgroundWorker.RunWorkerCompleted"/> changes. Notify of any errors, cancelation or 
        /// completion of the operation.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> object that fired the event.</param>
        /// <param name="e">The <see cref="RunWorkerCompletedEventArgs"/> event fired.</param>
        void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                OnFailed();
            }
            else
            {
                if (e.Cancelled)
                {
                    OnCancelled();
                }
                else
                {
                    OnCompleted();
                }
            }

            _dispatcher.BeginInvoke(new Action(OnCanExecuteChanged));
        }

        #endregion
    }
}